package com.ddw.meedeeuh.shared.database.sqlite;

import java.io.File;
import java.util.logging.Logger;

import com.almworks.sqlite4java.SQLiteConnection;
import com.almworks.sqlite4java.SQLiteException;
import com.almworks.sqlite4java.SQLiteQueue;
import com.almworks.sqlite4java.SQLiteStatement;
import com.ddw.meedeeuh.shared.database.DatabaseException;
import com.ddw.meedeeuh.shared.database.DatabaseVersionException;

/**
 * Represents an adapter for accessing a sqlite queue, the adapter
 * manages a single sqlite queue instance and handles scenarios
 * where a sqlite database doesn't exist
 * @author cmcnicholas
 *
 */
public abstract class SQLiteQueueAdapter {

	private final Logger logger;
	private final File file;
	private SQLiteQueue queue = null;
	private Integer version = null;

	/**
	 * Creates a new sqlite queue adapter instance
	 * @param file the file to access or create as a sqlite database
	 * @param logger the logger to log to
	 */
	public SQLiteQueueAdapter(File file, Logger logger) {
		if(file == null) {
			throw new IllegalArgumentException("The file parameter cannot be null");
		}
		if(logger == null) {
			throw new IllegalArgumentException("The logger parameter cannot be null");
		}

		this.file = file;
		this.logger = logger;
	}

	/**
	 * Gets a flag indicating if the queue is cached
	 * @return a cached flag
	 */
	public boolean isQueueCached() {
		return this.queue != null;
	}

	/**
	 * Gets or creates the sqlite queue, on first call this method will:
	 * <ul>
	 * <li>Create the sqlite database if new</li>
	 * <li>Create the meedeeuh tables and indexes if new</li>
	 * <li>Check the meedeuh database version</li>
	 * <li>Create a sqlite queue to handle sqlite jobs and cache it for future use</li>
	 * </ul>
	 * @param expectedVersion the expected database version to match
	 * @return a sqlite queue instance
	 * @throws DatabaseVersionException if the sqlite database does not match the expected version
	 * @throws DatabaseException if there is a problem creating, accessing or executing on a sqlite database
	 */
	public SQLiteQueue getQueue(int expectedVersion) throws DatabaseVersionException, DatabaseException {
		if(expectedVersion < 0) {
			throw new IllegalArgumentException("The expected version parameter cannot be less than zero");
		}

		// Check the queue isn't cached
		if(isQueueCached() == false) {
			// Check file
			logger.fine("Checking if database exists");
			final boolean dbExists = getFile().exists();

			// Create queue
			logger.fine("Creating SQLite database queue");
			final SQLiteQueue q = new SQLiteQueue(getFile());
			q.start();

			// Create if necessary
			if(dbExists == false) {
				logger.fine("Database is new, attempting to create tables");
				final boolean success = q.execute(new SQLiteJobHelper<Boolean>(null, "create", logger) {

					@Override
					protected Boolean job(SQLiteConnection connection) throws Throwable {
						try {
							onCreate(connection);
							return true;
						} catch (Throwable e) {
							return false;
						}
					}

				}).complete();

				// Check if we succeeded
				if(success == false) {
					logger.severe("Failed to create database");
					throw new DatabaseException("Failed to create database");
				}

				logger.fine("Database created successfully");
			}

			// Get database version
			logger.fine("Checking SQLite database version");
			this.version = q.execute(new SQLiteJobHelper<Integer>(null, "version", logger) {

				@Override
				protected Integer job(SQLiteConnection connection) throws Throwable {
					final SQLiteStatement statement = connection.prepare("SELECT version FROM version ORDER BY version DESC");
					try {
						return statement.step() ? statement.columnInt(0) : null;
					} catch (SQLiteException e) {
						throw new DatabaseException("Failed to retrieve database version", e);
					} finally {
						statement.dispose();
					}
				}

			}).complete();
			logger.fine("SQLite database version: " + (this.version == null ? "Not Found" : this.version.toString()));

			// Check the version is valid
			if(getVersion() == null) {
				final String message = "SQLite database version not found";
				logger.severe(message);
				throw new DatabaseVersionException(message);
			} else if(getVersion() != expectedVersion) {
				final String message = String.format("The database version was %d but was expected to be %d", getVersion(), expectedVersion);
				logger.severe(message);
				throw new DatabaseVersionException(message);
			}

			// If we get here all is well, set the queue
			this.queue = q;
			logger.fine("SQLite database queue set correctly");
		}

		return this.queue;
	}

	/**
	 * Method raised when a new sqlite database is created and the tables and indexes
	 * are required to be setup
	 * @param connection the connection to the sqlite database
	 * @throws DatabaseException if there is a problem creating the necessary tables or indexes
	 */
	public abstract void onCreate(SQLiteConnection connection) throws DatabaseException;

	/**
	 * Gets the sqlite database version
	 * @return a version number or null
	 */
	public Integer getVersion() {
		return this.version;
	}

	/**
	 * Gets the sqlite file
	 * @return a file instance
	 */
	private File getFile() {
		return this.file;
	}

}
